package MonicaPoint;
use strict;
use warnings;
use Astro::Time;
use ATNF::MoniCA;
use POSIX qw(strtod);

# The constructor function.
sub new {
    my $class = shift;
    my $pointname = shift;
    if ($pointname eq '') {
	# We need a point name to continue.
	return 0;
    }
    my $server = shift;
    if ($server eq '') {
	# Assume a connection to Narrabri's MoniCA.
	$server = 'monhost-nar.atnf.csiro.au';
    }

    # Create an anonymous hashref.
    my $self = {
	_pointname => $pointname,
	_server => $server,
	_values => [ { bat => 0 }, { bat => 0 } ],
	_maxnpoints => 1
    };

    bless($self, $class);
    # Get the details immediately.
    obtain_details($self);

    return $self;
}

# Connect to the server.
sub connect {
    my $self = shift;
    
    return monconnect($self->{_server});
}

# Get the details for this point.
sub obtain_details {
    my $self = shift;

    # Return quickly if we've already got this information.
    if (defined $self->{_updatetime}) {
	return {
	    point => $self->{_point},
	    updatetime => $self->{_updatetime},
	    units => $self->{_units},
	    description => $self->{_description}
	};
    }

    # Get this information.
    my @pointarr = ( $self->{_pointname} );
    
    my $conn = $self->connect();
    my @pointdesc = mondetails($conn, @pointarr);

    # Store for faster retrieval next time.
    $self->{_point} = $pointdesc[0]->point;
    $self->{_updatetime} = $pointdesc[0]->updatetime;
    $self->{_units} = $pointdesc[0]->units,
    $self->{_description} = $pointdesc[0]->description;

    return $pointdesc[0];
}

# Keep a new value.
sub keep_value {
    my $self = shift;
    my $pointval = shift;

    # Check if the time is already the same for the current value.
    if ($pointval->{bat} eq $self->{_values}[$#{$self->{_values}}]->{bat}) {
	return;
    }
    
    if ($self->{_maxnpoints} == 1) {
	# We just keep this value and the last value.
	$self->{_values}[0] = $self->{_values}[1];
	$self->{_values}[1] = $pointval;
    } else {
	push @{$self->{_values}}, $pointval;
	if ($#{$self->{_values}} > $self->{_maxnpoints}) {
	    shift @{$self->{_values}};
	}
    }

    return;
}

# Ask MoniCA for the latest value for this point.
sub get_value {
    my $self = shift;

    my @pointarr = ( $self->{_pointname} );

    my $conn = $self->connect();
    my @pointval = monpoll2($conn, @pointarr);

    # Reverse the errorstate.
    my $rval = {
	bat => $pointval[0]->bat,
	val => $pointval[0]->val,
	errorstate => reverselogic($pointval[0]->errorstate)
    };

    # Store the value.
    $self->keep_value($rval);

    # Return the value.
    return $rval;
}
    
# Return the latest obtained value.
sub value {
    my $self = shift;

    return $self->{_values}[$#{$self->{_values}}];
}

# Return the value obtained immediately prior to the latest value.
sub lastvalue {
    my $self = shift;

    return $self->{_values}[$#{$self->{_values}} - 1];
}

# Return all the values we hold.
sub allvalues {
    my $self = shift;

    if ($self->{_maxnpoints} == 1) {
	# If we are only supposed to be holding a single point,
	# we only return a single point.
	return $self->value();
    } else {
	return $self->{_values};
    }
}

# Return a JSON-compatible string with our value(s).
sub publishJSON {
    my $self = shift;
    my $vtype = shift || '';

    # Get the right value.
    my $pval;
    if ($vtype eq 'last') {
	$pval = $self->lastvalue();
    } elsif ($vtype eq 'all') {
	$pval = $self->allvalues();
    } else {
	$pval = $self->value();
    }

    my $rstr = "name: \"".$self->{_pointname}."\",".
	"server: \"".$self->{_server}."\",".
	"value: ";
    if (ref($pval) eq 'ARRAY') {
	$rstr .= "[";
	for (my $i = 0; $i <= $#{$pval}; $i++) {
	    if ($i > 0) {
		$rstr .= ",";
	    }
	    $rstr .= makevalueJSON($pval->[$i]);
	}
	$rstr .= "]";
    } else {
	$rstr .= makevalueJSON($pval);
    }
    
    return $rstr;
}

# Reverse a logic name.
sub reverselogic {
    my $lval = shift;

    if ($lval eq 'true') {
	return 'false';
    } elsif ($lval eq 'false') {
	return 'true';
    }
}

# Make a JSON-compatible string for a single value hash.
sub makevalueJSON {
    my $valh = shift;
    
    my $rstr = "{";
    $rstr .= "time: ".bat2unixtime($valh->{bat},0).",".
	"value: ";
    # Check if our value can be converted into a number.
    if (is_numeric($valh->{val})) {
	$rstr .= $valh->{val};
    } else {
	$rstr .= "\"".$valh->{val}."\"";
    }
    $rstr .= ", errorState: ".$valh->{errorstate};
    $rstr .= "}";

    return $rstr;
}

# Convert a string into a number, if it can be.
sub getnum {
    my $str = shift;
    $str =~ s/^\s+//;
    $str =~ s/\s+$//;
    $! = 0;
    my ($num, $unparsed) = strtod($str);
    if (($str eq '') || ($unparsed != 0) || $!) {
	return;
    } else {
	return $num;
    }
}

# Check if a string can be converted into a number.
sub is_numeric {
    defined scalar &getnum($_[0])
}

1; # Required if we've written this as a module.
